#include "stdafx.h"
#include "PakArchive.h"

#include <stdio.h>

using namespace QuakeSandbox;
using namespace Ogre;

//-------------------------------------------------------
//Code for reading pack files.  
//Adapted from Q1 source code, with CRC checking disabled
//--------------------------------------------------------

#define MAX_FILES_IN_PACK       2048
#define PAK0_COUNT              339
#define PAK0_CRC                32981

//
// on disk
//
typedef struct
{
	char    name[56];
	int             filepos, filelen;
} dpackfile_t;

typedef struct
{
	char    id[4];
	int     dirofs;
	int     dirlen;
} dpackheader_t;


//
// in memory
//
typedef struct
{
	QS::String     name;
	int             filepos, filelen;
} packfile_t;

typedef struct pack_s
{
	QS::String     filename;
	FILE*             handle;
	int             numfiles;
	packfile_t      *files;
} pack_t;

static pack_t* LoadPackFile (const QS::String& packfile)
{
	dpackheader_t   header;
	int                             i;
	packfile_t              *newfiles;
	int                             numpackfiles;
	pack_t                  *pack;
	FILE*                           packhandle;
	dpackfile_t             info[MAX_FILES_IN_PACK];
	bool com_modified;

	packhandle = fopen(packfile.c_str(), "rb");
	if (!packhandle)
	{
		OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND, 
			        (packfile + " not found"),
					__FUNCTION__);
	}
	
	fread((void *)&header, sizeof(header), 1, packhandle);
	
	if (header.id[0] != 'P' || header.id[1] != 'A' ||
	    header.id[2] != 'C' || header.id[3] != 'K')
	{
		OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND, 
			        (packfile + " is not a packfile"), 
					__FUNCTION__);
	}
	
	numpackfiles = header.dirlen / sizeof(dpackfile_t);

	if (numpackfiles > MAX_FILES_IN_PACK)
	{
		OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
			        (packfile + " has too many files"),
					__FUNCTION__);
	}

	if (numpackfiles != PAK0_COUNT)
		com_modified = true;    // not the original file

	newfiles = new packfile_t[numpackfiles];

	fseek(packhandle, header.dirofs, SEEK_SET);
	fread((void *)info, header.dirlen, 1, packhandle);

// parse the directory
	for (i=0 ; i<numpackfiles ; i++)
	{
		newfiles[i].name = info[i].name;
		newfiles[i].filepos = info[i].filepos;
		newfiles[i].filelen = info[i].filelen;
	}

	pack = new pack_t();
	pack->filename =  packfile;
	pack->handle = packhandle;
	pack->numfiles = numpackfiles;
	pack->files = newfiles;
	
	std::ostringstream message;

	message << "Added packfile " << packfile << "(" << numpackfiles << " files)\n";

	LogManager::getSingleton().logMessage(message.str());

	return pack;
}

static void UnLoadPackFile (pack_t* pack)
{
	fclose(pack->handle);
	delete[] pack->files;
	delete pack;
}

packfile_t* FindFileEntry(const pack_t* pack, const QS::String& filename)
{
	for (int i=0 ; i<pack->numfiles ; i++)
	{
		if (pack->files[i].name == filename)
			return &pack->files[i];
	}

	return 0;
}

// -------------------------
//PakArchive implementation
//---------------------------

namespace QuakeSandbox
{
	struct PakArchive::PackHandle
	{			
		pack_t* pack;
	};
}

PakArchive::PakArchive( const Ogre::String& name, const Ogre::String& archType )
	: Archive(name, archType), mPack(new PackHandle())
{
	
}

PakArchive::~PakArchive(){ unload(); }

bool PakArchive::isCaseSensitive(void) const
{
	return true;
}

void PakArchive::load()
{
	mPack->pack = LoadPackFile(mName);
}

void PakArchive::unload()
{
	if (mPack->pack)
	{
		UnLoadPackFile(mPack->pack);
		mPack->pack = NULL;
	}
}

Ogre::DataStreamPtr PakArchive::open(const Ogre::String& filename) const
{
	Ogre::DataStream* stream = 0;
	FILE* handle = mPack->pack->handle;

	if (exists(filename))
	{
		//find entry in pak file
		packfile_t* found_file = FindFileEntry(mPack->pack, filename);
		//seek to the file
		fseek(handle, found_file->filepos, SEEK_SET);
		//read from file into memory
		char* buf = new char[found_file->filelen];
		if (fread(buf, found_file->filelen, 1, handle) != 1)
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, "Could not read from pak file", "");
		}

		stream = new Ogre::MemoryDataStream(buf, found_file->filelen, true);
	}

	return DataStreamPtr(stream);
}

Ogre::StringVectorPtr PakArchive::list(bool /*recursive*/, bool /*dirs*/)
{
	Ogre::StringVector* fileList = new Ogre::StringVector();

	for (int i=0 ; i<mPack->pack->numfiles ; i++)
	{
		fileList->push_back(mPack->pack->files[i].name);
	}

	return StringVectorPtr(fileList);
}

Ogre::FileInfoListPtr PakArchive::listFileInfo(bool /*recursive*/, bool /*dirs*/)
{
	Ogre::FileInfoList* infoList = new Ogre::FileInfoList();

	for (int i=0 ; i<mPack->pack->numfiles ; i++)
	{
		FileInfo info;		
		info.archive = this;
		info.filename = info.path = info.basename = mName;
		info.compressedSize = info.uncompressedSize = mPack->pack->files[i].filelen;

		infoList->push_back(info);
	}

	return FileInfoListPtr(infoList);
}


Ogre::StringVectorPtr PakArchive::find(const Ogre::String& pattern, 
									   bool /*recursive*/,
									   bool /*dirs*/)
{	
	Ogre::StringVectorPtr files = list();
	Ogre::StringVectorPtr matchingFiles = Ogre::StringVectorPtr(new Ogre::StringVector());

	for (size_t i=0 ; i<files->size() ; i++)
	{
		Ogre::String candidate = files->at(i);
		if (Ogre::StringUtil::match(candidate, pattern))
			matchingFiles->push_back(candidate);
	}

	return matchingFiles;
}

Ogre::FileInfoListPtr PakArchive::findFileInfo(const Ogre::String& pattern, 
											   bool /*recursive*/, 
											   bool /*dirs*/)
{
	return FileInfoListPtr(new FileInfoList());
}

bool PakArchive::exists(const Ogre::String& filename)
{
	return existsImpl(filename);
}


bool PakArchive::exists(const Ogre::String& filename) const
{
	return existsImpl(filename);
}

bool PakArchive::existsImpl(const Ogre::String& filename) const
{
	return FindFileEntry(mPack->pack, filename) == 0 ? false : true;
}


// -------------------------
//PakArchiveFactory implementation
//---------------------------

const Ogre::String& PakArchiveFactory::getType(void) const
{
	static Ogre::String type = "Pak";

	return type;
}